/*
 * DllEntry point functions for ffmpeg encoder and decoder
 * Copyright (c) 2004 Andrew Ivanov <jho@nata-info.ru>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

/**
 * @file dllentry.c
 */

#include <windows.h>
#include "libavcodec/avcodec.h"
#include "compiler.h"

//add this macro to project predefinitions
//#define USE_HEAP_FOR_EVERY_SESSION

//ffmpeg h264 decoder
extern AVCodec h264_decoder;

CRITICAL_SECTION g_csStaticDataLock;

//the session type,dll user keep it for every decode session
typedef int H264DECSESSION;

//the h264 decode session structure,it stores the decode session data and infomation 
typedef struct tagH264DecodeSession
{
	H264DECSESSION session;				//the session identifier
	AVCodec *codec;						//the ffmpeg h264 decoder
	AVCodecContext *context;			//the ffmpeg decode session context
	AVFrame *picture;					//the ffmpeg decoded frame
	int frame;							//how many frames decoded
	struct tagH264DecodeSession *next;	//pointer to next session in list
	HANDLE heap;
	int last_frame;
	int repeat_time;
} H264DecodeSession, *LPH264DecodeSession;

//the decode session list head pointer,all decode session stores in the list
LPH264DecodeSession g_pSessionHeader = NULL;

//add the new session to the tail of the session list
void AddNewSessioin(H264DecodeSession *pNewSession)
{

	EnterCriticalSection(&g_csStaticDataLock);

	if (g_pSessionHeader == NULL)
	{
		g_pSessionHeader = pNewSession;
	}
	else
	{
		H264DecodeSession *tailPtr = g_pSessionHeader;
		while (tailPtr->next != NULL)
			tailPtr = tailPtr->next;

		tailPtr->next = pNewSession;
	}

	LeaveCriticalSection(&g_csStaticDataLock);
}

//find the session with the specific session id in the list
LPH264DecodeSession FindSession(H264DECSESSION session)
{
	LPH264DecodeSession pSession = NULL;

	EnterCriticalSection(&g_csStaticDataLock);

	pSession = g_pSessionHeader;

	while (pSession != NULL && pSession->session != session) 
		pSession = pSession->next;

	LeaveCriticalSection(&g_csStaticDataLock);

	return pSession;
}

//remove the session with specific session id from the list(but doesn't release it)
LPH264DecodeSession RemoveSession(H264DECSESSION session)
{
	LPH264DecodeSession result = NULL;
	LPH264DecodeSession pSession = NULL;

	EnterCriticalSection(&g_csStaticDataLock);

	pSession = g_pSessionHeader;

	if (g_pSessionHeader == NULL)
	{
		LeaveCriticalSection(&g_csStaticDataLock);
		return NULL;
	}
	else if (g_pSessionHeader->session == session)
	{
		g_pSessionHeader = g_pSessionHeader->next;

		LeaveCriticalSection(&g_csStaticDataLock);
		return pSession;
	}

	while (pSession->next != NULL && pSession->next->session != session)
		pSession = pSession->next;

	if (pSession->next == NULL)
	{
		LeaveCriticalSection(&g_csStaticDataLock);
		return NULL;
	}

	result = pSession->next;
	pSession->next = result->next;

	LeaveCriticalSection(&g_csStaticDataLock);

	return result;
}

//release all resources of a session 
void InternalCloseSession(LPH264DecodeSession pSession)
{
	if (pSession == NULL)
		return;

	__try
	{
		avcodec_close(pSession->context);
#ifdef USE_HEAP_FOR_EVERY_SESSION
#else
		av_free(pSession->context);
		av_free(pSession->picture);
#endif
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
	}

	av_free(pSession);
}



#define DLLSPEC __declspec(dllexport)

//see h264decsdk.h for description of all exported api
DLLSPEC H264DECSESSION __cdecl CreateDecodeSession(unsigned int width, unsigned int height, int streambased)
{
	static H264DECSESSION next = 0;			//id for next session
	LPH264DecodeSession pNewSession = NULL;

	++next;

	//allocate and initialize the new session
	pNewSession = (LPH264DecodeSession) av_malloc(sizeof(H264DecodeSession));
	memset(pNewSession, 0, sizeof(H264DecodeSession));

	//OutputDebugStr("new session allocated\n");

	pNewSession->session = next;
	register_avcodec(&h264_decoder);
	pNewSession->codec = avcodec_find_encoder(CODEC_ID_H264);;
#ifdef USE_HEAP_FOR_EVERY_SESSION
	pNewSession->heap = HeapCreate(0, 64 * 1024, 0);
#else
	pNewSession->context = avcodec_alloc_context();
#endif
	if (streambased != 0)
	{
		pNewSession->context->flags |= CODEC_FLAG_TRUNCATED;	
		pNewSession->context->flags2 |= CODEC_FLAG2_CHUNKS;	
	}
	pNewSession->context->coded_width = width;
	pNewSession->context->coded_height = height;

	if (avcodec_open(pNewSession->context, pNewSession->codec) < 0) 
	{
		//the initialization failed.
		av_free(pNewSession->context);
		av_free(pNewSession);

		return 0;
	}

	//OutputDebugStr("h264 decoder opened.\n");

#ifdef USE_HEAP_FOR_EVERY_SESSION
#else
	pNewSession->picture = avcodec_alloc_frame();
#endif

	//add new session to list
	AddNewSessioin(pNewSession);

	//OutputDebugStr("new session added.\n");

	return pNewSession->session;
}

DLLSPEC H264DECSESSION __cdecl CreateDecodeSessionNoSize(int streambased)
{
	return CreateDecodeSession(0, 0, streambased);
}

DLLSPEC int __cdecl GetVideoSize(H264DECSESSION session, int *width, int *height)
{
	LPH264DecodeSession pSession = FindSession(session);

	if (pSession == NULL)
		return -1;

	if (width == NULL || height == NULL)
		return -3;

	if (pSession->context->width == 0 && pSession->context->coded_width == 0)
		return -2;

	*width = (pSession->context->coded_width == 0) ? pSession->context->width : pSession->context->coded_width;
	*height = (pSession->context->coded_width == 0) ? pSession->context->height : pSession->context->coded_height;

	return 0;
}

DLLSPEC void __cdecl CloseDecodeSession(H264DECSESSION session)
{
	LPH264DecodeSession pSession = RemoveSession(session);

	if (pSession != NULL)
		InternalCloseSession(pSession);
}

DLLSPEC void __cdecl ClearAllSession()
{
	LPH264DecodeSession pSession = NULL;

	EnterCriticalSection(&g_csStaticDataLock);

	pSession = g_pSessionHeader;
	if (pSession == NULL)
	{
		LeaveCriticalSection(&g_csStaticDataLock);
		return;
	}

	while (pSession != NULL)
	{
		LPH264DecodeSession next = pSession->next;

		InternalCloseSession(pSession);

		pSession = next;
	}

	g_pSessionHeader = NULL;

	LeaveCriticalSection(&g_csStaticDataLock);
}

DLLSPEC int __cdecl DecodeData(H264DECSESSION session, unsigned char *inbuf, unsigned int size, 
							   int *getframe,
							   unsigned char *ybuf, unsigned char *ubuf, unsigned char *vbuf,
							   int dstlinesize, int reverse)
{
	LPH264DecodeSession pSession = FindSession(session);
	int len = 0;
	int i = 0, n = 0;
	unsigned char *src = NULL;
	int linesize = 0;
	int width = 0;
	int halfdstlinesize = 0;

	char debugstr[256];

	//checks parameters
	if (pSession == NULL)
		return -1;

	if (inbuf == NULL || size == 0)
		return -6;

	//if (ybuf == NULL || ubuf == NULL || vbuf == NULL)
	//	return -5;

	//decode frame
	__try 
	{
		len = avcodec_decode_video(pSession->context, pSession->picture, getframe, inbuf, size);

	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		*getframe = 0;
		return -2;
	}
	if (len < 0)
		return -2;	//decode failed

#undef sprintf
	//sprintf(debugstr, "bitstream seq = %d, display seq = %d, getframe = %d\n", pSession->picture->coded_picture_number, pSession->picture->display_picture_number, *getframe);
	//OutputDebugStr(debugstr);
	if (pSession->picture->coded_picture_number < pSession->last_frame)
	{
		//OutputDebugStr("discard delayed frame.\n");

		return len;
	}
	else if (pSession->picture->coded_picture_number == pSession->last_frame)
	{
		pSession->repeat_time++;
		if (pSession->repeat_time >= 10)
			return -2;
	}
	else
	{
		pSession->last_frame = pSession->picture->coded_picture_number;
		pSession->repeat_time = 0;
	}

	if (dstlinesize == 0)
		dstlinesize = pSession->context->width;

	if (dstlinesize < pSession->context->width)
		return -4;

	if (*getframe == 0)
		return len;

	pSession->frame++;

	if (ybuf == NULL || ubuf == NULL || vbuf == NULL)
		return len;

	//copy to output buffer
	halfdstlinesize = dstlinesize / 2;
	if (reverse)
	{
		//copy y channel
		width = pSession->context->width;
		linesize = pSession->picture->linesize[0];
		src = pSession->picture->data[0] + linesize * (pSession->context->height - 1);
		for (i = 0; i < pSession->context->height; ++i, src -= linesize, ybuf += dstlinesize)
			memcpy(ybuf, src, width);

		//copy u channel
		width /= 2;
		linesize = pSession->picture->linesize[1];
		src = pSession->picture->data[1] + linesize * (pSession->context->height / 2 - 1);
		for (i = pSession->context->height / 2; i; --i, src -= linesize, ubuf += halfdstlinesize)
			memcpy(ubuf, src, width);

		//copy v channel
		linesize = pSession->picture->linesize[2];
		src = pSession->picture->data[2] + linesize * (pSession->context->height / 2 - 1);
		for (i = pSession->context->height / 2; i; --i, src -= linesize, vbuf += halfdstlinesize)
			memcpy(vbuf, src, width);

	}
	else 
	{
		width = pSession->context->width;
		linesize = pSession->picture->linesize[0];
		src = pSession->picture->data[0];
		for (i = pSession->context->height; i; --i, src += linesize, ybuf += dstlinesize)
			memcpy(ybuf, src, width);

		width /= 2;
		linesize = pSession->picture->linesize[1];
		src = pSession->picture->data[1];
		for (i = pSession->context->height / 2; i; --i, src += linesize, ubuf += halfdstlinesize)
			memcpy(ubuf, src, width);

		linesize = pSession->picture->linesize[2];
		src = pSession->picture->data[2];
		for (i = pSession->context->height / 2; i; --i, src += linesize, vbuf += halfdstlinesize)
			memcpy(vbuf, src, width);
	}

	return len;

}

DLLSPEC int __cdecl DecodeFrame(H264DECSESSION session, unsigned char *inbuf, unsigned int size,
						unsigned char *ybuf, unsigned char *ubuf, unsigned char *vbuf, 
						int dstlinesize, int reverse)
{
	int getframe = 0;

	int len = DecodeData(session, inbuf, size, &getframe, ybuf, ubuf, vbuf, dstlinesize, reverse);

	if (getframe == 0)
		return -3;

	return len;
}

// --- standard WIN32 entrypoints --------------------------------------
BOOL WINAPI DllEntryPoint(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	static int count=0;
	char pomS[40];
	switch (dwReason)
	{
	case DLL_PROCESS_ATTACH:
		count++;
		//snprintf(pomS,40,"libavcodec: %i %i\n",count,hInstance);OutputDebugString(pomS);
		DisableThreadLibraryCalls(hInstance);
		InitializeCriticalSection( &g_csStaticDataLock );

		avcodec_init();

		break;

	case DLL_PROCESS_DETACH:
		ClearAllSession();

		count--;
		//snprintf(pomS,40,"libavcodec: %i %i\n",count,hInstance);OutputDebugString(pomS);
		if (count<=0)
			av_free_static();

		DeleteCriticalSection( &g_csStaticDataLock );

#ifdef _DEBUG
		OutputDebugString("=====>Dump memory leak for H264Dec SDK.\n");
		_CrtDumpMemoryLeaks();
#endif
		break;
	}
	return TRUE;
}

static char av_datetime[]=__DATE__" "__TIME__;
void getVersion(char **version,char **build,char **datetime,const char* *license)
{
	if (version) *version=AV_STRINGIFY(LIBAVCODEC_VERSION)", "COMPILER COMPILER_X64;
	if (build) *build=AV_STRINGIFY(LIBAVCODEC_BUILD);
	if (datetime) *datetime=av_datetime;
	if (license) *license="";
}


//a stub that call the original entry point function.
//that function was specified by /ENTRY option in compile configuration,
//but it won't do the static initialization for the library 
BOOL APIENTRY DllMain( HMODULE hModule,
					  DWORD  ul_reason_for_call,
					  LPVOID lpReserved
					  )
{
	return DllEntryPoint(hModule, ul_reason_for_call, lpReserved);
}
